OKHttp的缓存策略

OKhttp的缓存是通过CacheInterceptor这个拦截器实现的,它的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
public final class CacheInterceptor implements Interceptor {
final InternalCache cache;

public CacheInterceptor(InternalCache cache) {
this.cache = cache;
}

@Override public Response intercept(Chain chain) throws IOException {
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;//先从缓存中取出Request对应的Response,可能为null

long now = System.currentTimeMillis();
//通过Request和Response构造CacheStrategy
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
//缓存监控
if (cache != null) {
cache.trackResponse(strategy);
}
//cacheCandidate无效,关闭它
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}

// If we're forbidden from using the network and the cache is insufficient, fail.
//networkReqeust和cacheResponse都为null表示禁止使用网络且缓存不够用,这是返回504响应
if (networkRequest == null && cacheResponse == null) {
return new Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(Util.EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
}

// If we don't need the network, we're done.
//缓存有效的情况,不需要进行网络请求
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))//配置Response的cacheResponse,但并不需要持有body所以这里剔除掉body
.build();
}

Response networkResponse = null;
try {
//进行网络请求
networkResponse = chain.proceed(networkRequest);
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}

// If we have a cache response too, then we're doing a conditional get.
//如果缓存也存在
if (cacheResponse != null) {
//响应304码表示服务器认为资源未改变
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
Response response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis())
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.body().close();

// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()).
cache.trackConditionalCacheHit();
cache.update(cacheResponse, response);
return response;//返回cacheResponse的内容
} else {
closeQuietly(cacheResponse.body());
}
}
//网络请求的Response
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();

if (cache != null) {
//将最新的请求存储到缓存中
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}

if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}

return response;//返回结果
}

//将Response的body剔除
private static Response stripBody(Response response) {
return response != null && response.body() != null
? response.newBuilder().body(null).build()
: response;
}
……
}

CacheInterceptor内部持有一个InternalCache,它负责缓存的存取,而CacheStrategy用来控制缓存的存取,决定什么时候用缓存,什么时候使用网络进行请求。它是通过其内部的networkRequest和cacheResponse决定的,当networkRequest的值为null表示缓存有效不进行网络请求,而当cacheResponse为null是表示缓存失效或者未命中,需要进行网络请求。当它们都不为null,则根据响应结果来判断缓存是否失效。

CacehStrategy通过Request和候选的缓存进行构造的,我们看看它的内部是如何实现的

CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
public final class CacheStrategy {
/** The request to send on the network, or null if this call doesn't use the network. */
public final @Nullable Request networkRequest;

/** The cached response to return or validate; or null if this call doesn't use a cache. */
public final @Nullable Response cacheResponse;

public static class Factory {
final long nowMillis;
final Request request;
final Response cacheResponse;

/** The server's time when the cached response was served, if known. */
private Date servedDate;
private String servedDateString;

/** The last modified date of the cached response, if known. */
private Date lastModified;
private String lastModifiedString;

/**
* The expiration date of the cached response, if known. If both this field and the max age are
* set, the max age is preferred.
*/
private Date expires;

/**
* Extension header set by OkHttp specifying the timestamp when the cached HTTP request was
* first initiated.
*/
private long sentRequestMillis;

/**
* Extension header set by OkHttp specifying the timestamp when the cached HTTP response was
* first received.
*/
private long receivedResponseMillis;

/** Etag of the cached response. */
private String etag;

/** Age of the cached response. */
private int ageSeconds = -1;

public Factory(long nowMillis, Request request, Response cacheResponse) {
this.nowMillis = nowMillis;
this.request = request;
this.cacheResponse = cacheResponse;//将候选的缓存保存下来
//从候选的缓存中解析出一些信息
if (cacheResponse != null) {
this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
Headers headers = cacheResponse.headers();//获取到缓存响应头部
for (int i = 0, size = headers.size(); i < size; i++) {
String fieldName = headers.name(i);
String value = headers.value(i);
if ("Date".equalsIgnoreCase(fieldName)) {
servedDate = HttpDate.parse(value);//得到服务器的日期
servedDateString = value;
} else if ("Expires".equalsIgnoreCase(fieldName)) {
expires = HttpDate.parse(value);//得到失效时间
} else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
lastModified = HttpDate.parse(value);//得到资源上次修改的时间
lastModifiedString = value;
} else if ("ETag".equalsIgnoreCase(fieldName)) {
etag = value;//得到资源对应的ETag
} else if ("Age".equalsIgnoreCase(fieldName)) {
ageSeconds = HttpHeaders.parseSeconds(value, -1);
}
}
}
}

/**
* Returns a strategy to satisfy {@code request} using the a cached response {@code response}.
*/
public CacheStrategy get() {
CacheStrategy candidate = getCandidate();
if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
// We're forbidden from using the network and the cache is insufficient.
return new CacheStrategy(null, null);
}
return candidate;
}

/** Returns a strategy to use assuming the request can use the network. */
private CacheStrategy getCandidate() {
// No cached response.
//没有命中的缓存,使用网络进行请求
if (cacheResponse == null) {
return new CacheStrategy(request, null);
}

// Drop the cached response if it's missing a required handshake.
//如果是https,但是缓存中缺少了握手信息,同样进行网络请求
if (request.isHttps() && cacheResponse.handshake() == null) {
return new CacheStrategy(request, null);
}

// If this response shouldn't have been stored, it should never be used
// as a response source. This check should be redundant as long as the
// persistence store is well-behaved and the rules are constant.
//如果该缓存是不应该被缓存的,则同样进行网络请求
if (!isCacheable(cacheResponse, request)) {
return new CacheStrategy(request, null);
}
//得到请求头部的CacheControl
CacheControl requestCaching = request.cacheControl();
//如果不进行缓存则直接进行网络请求
if (requestCaching.noCache() || hasConditions(request)) {
return new CacheStrategy(request, null);
}

long ageMillis = cacheResponseAge();
long freshMillis = computeFreshnessLifetime();

if (requestCaching.maxAgeSeconds() != -1) {
freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
}

long minFreshMillis = 0;
if (requestCaching.minFreshSeconds() != -1) {
minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
}

long maxStaleMillis = 0;
CacheControl responseCaching = cacheResponse.cacheControl();
if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
}
//可以缓存且缓存有效,不需要进行网络请求
if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
Response.Builder builder = cacheResponse.newBuilder();
if (ageMillis + minFreshMillis >= freshMillis) {
builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
}
long oneDayMillis = 24 * 60 * 60 * 1000L;
if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
}
return new CacheStrategy(null, builder.build());
}

// Find a condition to add to the request. If the condition is satisfied, the response body
// will not be transmitted.
String conditionName;
String conditionValue;
if (etag != null) {//etag不为null
conditionName = "If-None-Match";
conditionValue = etag;//将If-None-Match配合etag进行请求,服务端可以基于ETag判断资源是否匹配。
} else if (lastModified != null) {//在请求头部添加If-Modified-Since结合lastModifiedString判断资源是否被修改,如果未修改返回304
conditionName = "If-Modified-Since";
conditionValue = lastModifiedString;
} else if (servedDate != null) {//在请求头部添加If-Modified-Since
conditionName = "If-Modified-Since";
conditionValue = servedDateString;
} else {
return new CacheStrategy(request, null); // No condition! Make a regular request.
}

Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);

Request conditionalRequest = request.newBuilder()
.headers(conditionalRequestHeaders.build())
.build();
return new CacheStrategy(conditionalRequest, cacheResponse);
}
}
}

在getCandidate方法中实现了构造CacheStrategy实例的逻辑,总的来说:

一 使用网络进行请求的包括,即networkRequest != null :

  1. 缓存未命中
  2. https请求缺少handshake信息
  3. request指定的CacheControl不进行缓存
  4. 缓存的Response头部没有缓存相关的信息如ETag,If-None-Match,If-Modified-Since等

二 使用缓存请求的情况,即cacheResponse != null

  1. 缓存有效,未超过其失效时间

三 networkRequest != null 且 cacheResponse != null

  1. 缓存是否有效,要根据响应结果来判断

缓存的写入

在CacheInterceptor内部它对Response进行缓存的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if (cache != null) {
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}

if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}

写入缓存是在Cache类中实现的,它内部持有一个internalCache,它正是CacheInterceptor拦截器内部的缓存引用。这里我们先看cache put方法的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Nullable CacheRequest put(Response response) {
String requestMethod = response.request().method();//获取requestMethod
//判断是否为失效的缓存
if (HttpMethod.invalidatesCache(response.request().method())) {
try {
remove(response.request());
} catch (IOException ignored) {
// The cache cannot be written.
}
return null;
}
//只对GET响应进行缓存
if (!requestMethod.equals("GET")) {
// Don't cache non-GET responses. We're technically allowed to cache
// HEAD requests and some POST requests, but the complexity of doing
// so is high and the benefit is low.
return null;
}

if (HttpHeaders.hasVaryAll(response)) {
return null;
}
//构造Entry
Entry entry = new Entry(response);
DiskLruCache.Editor editor = null;
try {
//得到DiskLruCache.Editor 可以看到OkHttp缓存是通过DiskLruCache实现的
editor = cache.edit(key(response.request().url()));
if (editor == null) {
return null;
}
entry.writeTo(editor);//写入缓存
return new CacheRequestImpl(editor);
} catch (IOException e) {
abortQuietly(editor);
return null;
}
}

关于DiskLruCache将在另外的篇章中介绍。

坚持原创技术分享,您的支持将鼓励我继续创作!